/**
 * @fileOverview 
 * Defines PolygonEditor class, which is a widget for editing polygons.
 * @author Igor Kulkin <igor.kulkin@gmail.com>
 */


dojo.require("dojox.gfx");
dojo.require("dojox.gfx.move");

var Point = geoPolygonUtils.geometry.Point;
var Segment = geoPolygonUtils.geometry.Segment;

function PolygonEditor(element) {
  this.element_ = element;

  var size = dojo.contentBox(this.element_);
  this.surface_ = dojox.gfx.createSurface(this.element_, size.w, size.h);

  this.movingVertex_ = null;

  this.polygon_ = [];
  this.modificationStack_ = [this.polygon_];
  this.modificationIndex_ = 0;

  this.polygonShape_ = null;
  this.vertexShapes_ = [];
  this.previewShapes_ = [];

  this.eventTokens_ = [];
}


PolygonEditor.prototype.initialize = function() {
  this.eventTokens_.push(dojo.connect(this.element_, 'onclick', this, this.onClickHandler_));
  this.eventTokens_.push(dojo.connect(this.element_, 'onmousemove', this, this.onMouseMoveHandler_));
  this.eventTokens_.push(dojo.connect(this.element_, 'onmouseout', this, this.onMouseOutHandler_));

  // cancel text selection and text dragging
  //dojo.connect(this.element_, "ondragstart",   dojo, "stopEvent");
  //dojo.connect(this.element_, "onselectstart", dojo, "stopEvent");
};


PolygonEditor.prototype.release = function() {
  dojo.forEach(this.eventTokens_, dojo.disconnect);
  this.eventTokens_ = [];
  this.surface_.clear();
};


PolygonEditor.prototype.getPolygon = function() {
  return this.polygon_.slice();
};


PolygonEditor.prototype.updatePolygon = function(newPolygon) {
  if (geoPolygonUtils.isValidPolygon(newPolygon)) {
    var oldPolygon = this.polygon_;
    this.setActivePolygon_(newPolygon);

    if (!geoPolygonUtils.polygonsEqual(oldPolygon, newPolygon)) {
      this.modificationIndex_++;
      if (this.modificationIndex_ < this.modificationStack_.length) {
        // Dropping all the further modifications.
        this.modificationStack_ = this.modificationStack_.slice(0, this.modificationIndex_);
      }
      this.modificationStack_.push(newPolygon);
    }
    return true;
  } else {
    this.updateShape_();
    return false;
  }
};


PolygonEditor.prototype.undo = function() {
  if (this.modificationIndex_ == 0) return false;
  this.modificationIndex_--;
  this.setActivePolygon_(this.modificationStack_[this.modificationIndex_]);
  return true;
};


PolygonEditor.prototype.redo = function() {
  if (this.modificationIndex_ + 1 >= this.modificationStack_.length) return false;
  this.modificationIndex_++;
  this.setActivePolygon_(this.modificationStack_[this.modificationIndex_]);
  return true;
};


PolygonEditor.prototype.getShapeCenter_ = function(shape) {
  var bbox = shape.getTransformedBoundingBox();
  return new Point((bbox[0].x + bbox[1].x + bbox[2].x + bbox[3].x) / 4,
                   (bbox[0].y + bbox[1].y + bbox[2].y + bbox[3].y) / 4);
};


PolygonEditor.prototype.getInsertPosition_ = function(point) {
  if (this.polygon_.length < 3) {
    return this.polygon_.length;
  }
  var minDistance = Infinity;
  var bestIndex = this.polygon_.length;
  for (var i = 0; i < this.polygon_.length; ++i) {
    var j = (i + 1) % this.polygon_.length;
    var seg = new Segment(this.polygon_[i], this.polygon_[j]);
    // The following is the formula, which based on experiments gives the most
    // intuitive results.
    var curDistance = seg.distanceToPoint(point) + seg.distanceToPoint(seg.line().projectPoint(point));
    if (curDistance < minDistance) {
      minDistance = curDistance;
      bestIndex = i + 1;
    }
  }
  return bestIndex;
};


PolygonEditor.prototype.clearPreviewShapes_ = function() {
  dojo.forEach(this.previewShapes_, function (shape) { shape.removeShape(); });
  this.previewShapes_ = [];
};


PolygonEditor.prototype.updatePreview_ = function(opt_point) {
  this.clearPreviewShapes_();
  if (opt_point && this.polygon_.length > 0) {
    var point = opt_point;
    var pos0;
    if (this.movingVertex_ == null) {
      pos0 = this.getInsertPosition_(point) - 1;
    } else {
      opt_point = this.getShapeCenter_(this.vertexShapes_[this.movingVertex_]);
      if (this.polygon_.length < 2) return;
      pos0 = this.movingVertex_ - 1;
    }
    if (pos0 < 0) {
      pos0 += this.polygon_.length;
    }
    var pos1;
    if (this.movingVertex_ == null) {
      pos1 = (pos0 + 1) % this.polygon_.length;
    } else {
      pos1 = (pos0 + 2) % this.polygon_.length;
    }
    
    var point0 = this.polygon_[pos0];
    this.previewShapes_.push(this.surface_.createLine({x1: point0.x, y1: point0.y, x2: point.x, y2: point.y}).setStroke({color: "blue", style: "Dash"}));
          
    if (pos0 != pos1) {
      var point1 = this.polygon_[pos1];
      this.previewShapes_.push(this.surface_.createLine({x1: point1.x, y1: point1.y, x2: point.x, y2: point.y}).setStroke({color: "blue", style: "Dash"}));
    }
  }
};


PolygonEditor.prototype.onVertexMoveStartCallback_ = function(idx) {
  var me = this;
  return function() {
    me.movingVertex_ = idx
  };
};


PolygonEditor.prototype.onVertexMoveStopCallback_ = function(idx) {
  var me = this;
  return function () {
    var point = me.getShapeCenter_(me.vertexShapes_[me.movingVertex_]);
    var newPolygon = me.polygon_.slice();
    newPolygon[me.movingVertex_] = point;
    me.updatePolygon(newPolygon);
    me.movingVertex_ = null;
    me.clearPreviewShapes_();
  };
};


PolygonEditor.prototype.setActivePolygon_ = function(polygon) {
  this.polygon_ = polygon;
  this.updateShape_();
};


PolygonEditor.prototype.updateShape_ = function() {
  if (this.polygonShape_) {
    this.polygonShape_.removeShape();
    this.polygonShape_ = null;
  }
  if (this.polygon_.length > 0) {
    var polygon = this.polygon_.concat([this.polygon_[0]]);
    this.polygonShape_ = this.surface_.createPolyline(polygon).setFill([255, 0, 0, 0.5]).setStroke("blue");
    this.polygonShape_.moveToBack();
  }
  dojo.forEach(this.vertexShapes_, function (shape) { shape.removeShape(); });
  var me = this;
  this.vertexShapes_ = [];
  for (var i = 0; i < this.polygon_.length; ++i) {
    var point = this.polygon_[i];
    var shape = this.surface_.createCircle({cx: point.x, cy: point.y, r: 5}).setStroke("black").setFill([255, 255, 0, 0.3]);
    this.vertexShapes_.push(shape);
    var moveable = new dojox.gfx.Moveable(shape);
    moveable.onMoveStart = me.onVertexMoveStartCallback_(i);
    moveable.onMoveStop = me.onVertexMoveStopCallback_(i);
  }
};


PolygonEditor.prototype.addVertex = function(point) {
  var newPolygon = this.polygon_.slice()
  if (this.polygon_.length < 3) {
    newPolygon.push(point);
  } else {
    var pos = this.getInsertPosition_(point);
    newPolygon.splice(pos, 0, point);
  }
  this.updatePolygon(newPolygon);
};


PolygonEditor.prototype.onClickHandler_ = function(event) {
  this.clearPreviewShapes_();
  var pos = dojo.coords(this.element_);
  var point = new Point(event.clientX - pos.x, event.clientY - pos.y);
  this.addVertex(point);
};


PolygonEditor.prototype.onMouseMoveHandler_ = function(event) {
  var pos = dojo.coords(this.element_);
  var point = new Point(event.clientX - pos.x, event.clientY - pos.y);
  this.updatePreview_(point);
};


PolygonEditor.prototype.onMouseOutHandler_ = function() {
  this.updatePreview_();
};
